// 1.基本数据类型定义方式：
const num: number = 1;//数字
const str: string = '1';//字符串
const bol: boolean = true;//布尔值
const und: undefined = undefined;//undefined
const nu: null = null;//null
const sym: symbol = Symbol()//symbol
const numBig: bigint = 100n;//大整形

//2. 基本数据类型运算
let str1: string = 'hello';
let str2: string = 'word'
str1 += str2;  //字符串拼接
let num1: number = 666;
let num2: number = 999;
let num3: number = num1 + num2;//数字相加

const bol1: boolean = false;
let unde: undefined = undefined;
let nul: null = null;
unde = nul;
let symb: symbol = Symbol('name');
let int1: bigint = 10n;
let int2: bigint = BigInt(90)
let int: bigint = int1 + int2;


//3. 新增的数据类型：any,unknown
let a: any = 123;//任意类型
// 允许被赋值为任意类型。
// 声明一个变量为任意值之后，对它的任何操作，返回的内容的类型都是任意值。
a = 'atr';
a = true;
a = [];
a.push();

let b: unknown = 123;//未声明类型的变量
// 变量如果在声明的时候，未指定其类型，那么它会被识别为任意值类型：
b = [];
b = {};
b = '123';
// b.push()类型“unknown”上不存在属性“push”。

// 4.类型推论：如果没有明确的指定类型，那么 TypeScript 会依照类型推论（Type Inference）的规则推断出一个类型。
let num4 = 123;
// num='123'不能将类型“string”分配给类型“number”。

let c;
c = 123;
c = 'str';
// a:string='122'“string”仅表示类型，但在此处却作为值使用。
// 总结：如果定义的时候没有赋值，不管之后有没有赋值，都会被推断成 any 类型而完全不被类型检查：


//5. 联合类型：（Union Types）表示取值可以为多种类型中的一种。
// 我们只能访问此联合类型的所有类型里共有的属性或方法：
let aa: number | string = '123'
let bb: string = '345'
let cc: string = aa + bb
aa = 123;
let dd: number | string = '456';
dd += aa;
// aa+=dd;不能将类型“string”分配给类型“number”
console.log(dd);//456123
console.log(aa);//123
let bool: boolean = false;
dd = dd + bool;
console.log(dd);//456123false
// 总结：在变量有具体值类型时一般不能把其他类型分类本类型
// 字符串可以整合其他类型的，把其他类型转为串形式整合

// 6.对象的类型——接口
// 在面向对象语言中，接口（Interfaces）是一个很重要的概念，
// 它是对行为的抽象，而具体如何行动需要由类（classes）去实现（implement）。

// eg1:
interface Person {
  name: string;
  sex: string;
  money: number;
  isMarry: boolean;
  age: number;
  readonly id: number;//只读属性,
  home?: boolean;//可选参数,可选属性
}

const person: Person = {
  name: "小可爱",
  sex: "18",
  money: 999999999999999,
  isMarry: false,
  age: 18,
  id: 999,//只能在创建的时候被赋值
  home: true,
};
// 赋值的时候，变量的形状必须和接口的形状保持一致。

// eg2:
interface P {
  name: string;
  age: number;
  child: Child;
}
interface Child {
  name: string;
  age: number
}
const p: P = {
  name: '华哥',
  age: 18,
  child: {
    name: 'bang',
    age: 18,
  }
}
const nidaye: Child = p.child
console.log(nidaye);

// 任意属性eg3:
// 一旦定义了任意属性，那么确定属性和可选属性的类型都必须是它的类型的子集：
interface Person2 {
  name: string;
  sex?: number;
  [propName: string]: any;
}
let tom: Person2 = {
  name: 'Tom',
  gender: 'male'
};

//6.数组的类型

// 数组的泛型
// array<string>代表的数组类型里面只有字符串
const arr1: Array<string> = ['1', '2'];

// 数组中只有数字
const arr2: Array<number> = [1, 2, 3]

// 数字，字符串
const arr3: Array<number | string> = [1, '2']


// 表示法二：// 「类型 + 方括号」表示法
const arr4: number[] = [1, 2, 3];
const arr5: (number | string)[] = [1, 2, 3, 'a', 'b']

// 表示法三：接口表达数组类型
interface Arr {
  [index: number]: number;

}

// 数组接口案例：
interface Child3 {
  name: string;
  age: number;
}

interface Person3 {
  name: string;
  children: Child3[];
}

const hua: Person3[] = [
  {
    name: 'hua',
    children: [{
      name: 'bang', age: 8
    }]
  }]

// any 在数组中的应用
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];


// 08函数的类型
// 有返回值
const sun1 = function (x: number, y: number): number {
  return x + y
}
// 无返回值
const sun2 = function (x: number, y: number): void {
  x += y
  console.log(x);
}

// 函数的类型（x:number,y:number）=>number
const sum3: (x: number, y: number) => number
  = function (x, y) {
    return x + y

  }
const sum4: (x: number) => void
  = function (x) {
    console.log(x);
  }
// 用接口文档定义函数类型
interface Sum {
  (x: number, y: number): number
}
const sum5: Sum = function (x, y) {
  return x + y
}
// 注意：可选参数：必须放在可选参数后面，输入多余的（或者少于要求的）参数，是不允许的
const sum6 = function (x: number, y: number, z?: string): number {
  return x + y
}
// 默认值
const sum7 = function (x: number = 2, y: number): number {
  return x + y

}
// 使用扩展符，ES6 中，可以使用 ...rest 的方式获取函数中的剩余参数（rest 参数）：
// rest 参数只能是最后一个参数
const sum8 = function (x: number, ...rest): void {
  console.log(x, rest);
}

// 重载：重载允许一个函数接受不同数量或类型的参数时，作出不同的处理。
// 解决问题：不能够精确的表达，输入为数字的时候，输出也应该为数字，输入为字符串的时候，输出也应该为字符串。

function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | boolean {
  if (typeof x === 'number') {
    return Number(x.toString().split("").reverse().join(""));
  }
  else if (typeof x === "string") {
    return x.split("").reverse().join("");
  }
}
let areverse = reverse(123456)
console.log(areverse++);
let breverse = reverse('abcdefghijk')
console.log(breverse);

// 类型断言：可以用来手动指定一个值的类型。

// eg1:
function fn(x: string | number) {
  console.log(x.toString());
  // console.log(x.split(''));
  // 类型“string | number”上不存在属性“split”。
  // 类型“number”上不存在属性“split”。

  // 将一个联合类型断言成更加具体的某个类型
  // x as string  将x的类型断言成string类型
  console.log((x as string).split('.'));
}
// 需要注意的是，类型断言只能够「欺骗」TypeScript 编译器，
// 无法避免运行时的错误，反而滥用类型断言可能会导致运行时错误：
// 总结：
// 联合类型可以被断言为其中一个类型
// 父类可以被断言为子类
// 任何类型都可以被断言为 any
// any 可以被断言为任何类型
// 双重断言：任何类型都可以被断言为 any，any 可以被断言为任何类型

// 类型的别名：类型别名用来给一个类型起个新名字。
type Name = string;//使用 type 创建类型别名。
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
  if (typeof n === 'string') {
    return n;
  } else {
    return n();
  }
}

// 尽可能的复用类型
type StrOrNum = string | number;
let nummy: StrOrNum = 123;
let strmy: StrOrNum = 'abc';
type Arrmy = string[];
let cmy: Arrmy = ['a', 'b']
let dmy: Arrmy = ['c', 'd']
let info: StrOrNum = '123456' + 123456


